﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading;

using RexToy;
using RexToy.Logging;
using RexToy.Copy;

namespace MGAS.Logic.Communication
{
    public sealed class Communicator : IDisposable
    {
        private static ILog _log = LogContext.GetLogger<Communicator>();

        private SerialPort _com;
        private byte[] _r_buffer;
        private byte[] _w_buffer;
        public Communicator()
        {
            _r_buffer = new byte[32];
            _w_buffer = new byte[1];
            InitCom();
        }

        private void InitCom()
        {
            var settings = Configuration.GetSerialPortSetting();
            _com = new SerialPort(settings[Configuration.PORT]);
            settings.Copy(_com);
        }

        public void Open()
        {
            CheckDisposed();
            if (!_com.IsOpen)
                _com.Open();
        }

        public void Close()
        {
            CheckDisposed();
            if (_com.IsOpen)
                _com.Close();
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private void CheckDisposed()
        {
            if (_disposed)
                throw new ObjectDisposedException("Communicator");
        }

        private bool _disposed;
        private void Dispose(bool disposing)
        {
            if (_disposed)
                return;

            if (disposing)
            {
                _com.Dispose();
            }

            _disposed = true;
        }

        ~Communicator()
        {
            Dispose(false);
        }

        #region Communicate to COM(SerialPort)

        public ISensorData Query(byte id, bool meta)
        {
            try
            {
                _w_buffer[0] = id;
                _com.Write(_w_buffer, 0, 1);
            }
            catch (TimeoutException)
            {
                _log.Warning("Write to [COM:{0}] timeout.", _com.PortName);
                return SensorDataFactory.CreateUnknownData(id);
            }
            catch (InvalidOperationException ioe)
            {
                _log.Warning("Write to [COM:{0}] get invalid operation error. -> {1}", _com.PortName, ioe.ToString());
                return SensorDataFactory.CreateUnknownData(id);
            }
            catch (Exception ex)
            {
                _log.Error("Write to [COM:{0}] get unknown error. -> {1}", _com.PortName, ex.ToString());
                return SensorDataFactory.CreateUnknownData(id);
            }

            try
            {
                Thread.Sleep(Configuration.GetReadLatency());

                if (_com.BytesToRead != 0)
                {
                    int readLen = _com.Read(_r_buffer, 0, _com.BytesToRead);
                    if (readLen != 6)
                    {
                        throw new ProtocolException(string.Format("Communication error, read [{0}] byte(s) data.", readLen));
                    }
                    return ParseData(id, _r_buffer, meta);
                }
                else
                {
                    _log.Warning("[Thread {0}]No data: lost contact with sensor [id={1}]", Thread.CurrentThread.ManagedThreadId, id);
                    return SensorDataFactory.CreateUnknownData(id);
                }
            }
            catch (ProtocolException pex)
            {
                _log.Warning("[Thread {0}]Contact error with sensor [id={1}]: {2}", Thread.CurrentThread.ManagedThreadId, id, pex.Message);

                return SensorDataFactory.CreateUnknownData(id);
            }
            catch (TimeoutException)
            {
                _log.Warning("[Thread {0}]Time out: lost contact with sensor [id={1}]", Thread.CurrentThread.ManagedThreadId, id);
                return SensorDataFactory.CreateUnknownData(id);
            }
            catch (Exception ex)
            {
                _log.Error("[Thread {0}]Unknown error with sensor [id={1}] -> {2}", Thread.CurrentThread.ManagedThreadId, id, ex.ToString());
                return SensorDataFactory.CreateUnknownData(id);
            }
        }
        #endregion

        private ISensorData ParseData(byte id, byte[] buffer, bool meta)
        {
            _log.Verbose("[Thread {0}][{1}=> {2},{3},{4},{5},{6},{7}]", Thread.CurrentThread.ManagedThreadId, id, buffer[0].ToString("X"), buffer[1].ToString("X"), buffer[2].ToString("X"), buffer[3].ToString("X"), buffer[4].ToString("X"), buffer[5].ToString("X"), buffer[6].ToString("X"));

            if (!meta)
            {
                int val = buffer[3] * 256 + buffer[4];
                if (buffer[3] > 127)
                    val = val - 65536;

                byte statusMask = 0x7C;
                PressureStatus status = (PressureStatus)(statusMask & buffer[5]);
                if (!status.IsValid())
                    status = PressureStatus.Error;

                byte unitMask = 0x03;
                PressureUnit unit = (PressureUnit)(buffer[5] & unitMask);
                if (!unit.IsValid())
                    unit = PressureUnit.Unknown;

                var data = SensorDataFactory.CreateDynamicData(id);
                data.Value = val;
                data.Unit = unit;
                data.Status = status;
                return data;
            }
            else
            {
                int building = buffer[0];
                if (building > 127)
                    building = building - 256;


                int floor = buffer[1];
                if (floor > 127)
                    floor = floor - 256;

                int line = (0xf0 & buffer[2]) >> 4;
                GasType gasType = (GasType)(0x0f & buffer[2]);

                var data = SensorDataFactory.CreateStaticData(id, building, floor, line, gasType);
                return data;
            }
        }
    }
}
